﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Runtime.InteropServices;

namespace ImageProcessing
{
    public partial class Form_Main : Form
    {
        public Form_Main()
        {
            InitializeComponent();

            Initialize();
        }

        private bool sourceAvailable;

        private void Initialize()
        {
            sourceAvailable = false;

            openFileDialog_LoadImage.Filter = "Bmp files (*.bmp;*.jpg;*.png)|*.bmp;*.jpg;*.png";
            openFileDialog_LoadImage.FilterIndex = 1;
            openFileDialog_LoadImage.RestoreDirectory = true;
        }

        private void button_LoadImage_Click(object sender, EventArgs e)
        {
            if (openFileDialog_LoadImage.ShowDialog() == DialogResult.OK)
            {
                string fileName = openFileDialog_LoadImage.FileName;
                pictureBox_SourceImage.Image = Image.FromFile(fileName);

                sourceAvailable = true;
            }
        }

        private void button_Mean_Click(object sender, EventArgs e)
        {
            if (!sourceAvailable)
            {
                MessageBox.Show("Please load source image first!");
                return;
            }

            Bitmap sourceImage = new Bitmap(pictureBox_SourceImage.Image);
            FormatImage(ref sourceImage);

            kernel = new int[,] { { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 } };
            Bitmap destImage = getTargetImage(sourceImage, false);

            pictureBox_ConvertedImage.Image = destImage;
            destImage.Save("d://test.jpg");

        }

        private void button_Edges_Click(object sender, EventArgs e)
        {
            if (!sourceAvailable)
            {
                MessageBox.Show("Please load source image first!");
                return;
            }

            Bitmap sourceImage = new Bitmap(pictureBox_SourceImage.Image);
            FormatImage(ref sourceImage);

            kernel = new int[,] { { 0, -1, 0 }, { -1, 4, -1 }, { 0, -1, 0 } };
            Bitmap destImage = getTargetImage(sourceImage, false);

            pictureBox_ConvertedImage.Image = destImage;
        }

        private void button_Sharpen_Click(object sender, EventArgs e)
        {
            if (!sourceAvailable)
            {
                MessageBox.Show("Please load source image first!");
                return;
            }

            Bitmap sourceImage = new Bitmap(pictureBox_SourceImage.Image);
            FormatImage(ref sourceImage);

            kernel = new int[,] { { 0, -1, 0 }, { -1, 5, -1 }, { 0, -1, 0 } };
            Bitmap destImage = getTargetImage(sourceImage, false);

            pictureBox_ConvertedImage.Image = destImage;
        }

        private void button_Med_Click(object sender, EventArgs e)
        {
            if (!sourceAvailable)
            {
                MessageBox.Show("Please load source image first!");
                return;
            }

            Bitmap sourceImage = new Bitmap(pictureBox_SourceImage.Image);
            FormatImage(ref sourceImage);

            Bitmap destImage = getTargetImage(sourceImage, true);

            pictureBox_ConvertedImage.Image = destImage;
            destImage.Save("d://test.jpg");
        }

        private void FormatImage(ref Bitmap image)
        {
            if (
                (image.PixelFormat != PixelFormat.Format24bppRgb) &&
                (image.PixelFormat != PixelFormat.Format8bppIndexed)
                )
            {
                image = image.Clone(new Rectangle(0, 0, image.Width, image.Height), PixelFormat.Format24bppRgb);
            }
        }

        private Bitmap getTargetImage(Bitmap sourceImage, bool isMedian)
        {
            // get image dimension
            int width = sourceImage.Width;
            int height = sourceImage.Height;
            PixelFormat format = sourceImage.PixelFormat;

            // lock source bitmap data
            BitmapData sourceData = sourceImage.LockBits(
                new Rectangle(0, 0, width, height),
                ImageLockMode.ReadOnly,
                format);

            // create new image
            Bitmap destImage = new Bitmap(width, height, format);

            // lock destination bitmap data
            BitmapData destData = destImage.LockBits(
                new Rectangle(0, 0, width, height),
                ImageLockMode.ReadWrite,
                format);

            // process the filter
            if (isMedian)
            {
                ProcessFilter_Median(sourceData, destData);
            }
            else
            {
                ProcessFilter(sourceData, destData);
            }

            // unlock destination images
            destImage.UnlockBits(destData);
            
            // unlock source image
            sourceImage.UnlockBits(sourceData);

            return destImage;
        }

        private int[,] kernel;
        private int size;
        private unsafe void ProcessFilter(BitmapData sourceData, BitmapData destData)
        {
            size = kernel.GetLength(0);

            // get source image size
            int width = sourceData.Width;
            int height = sourceData.Height;

            int stride = sourceData.Stride;
            int offset = stride - ((sourceData.PixelFormat == PixelFormat.Format8bppIndexed) ? width : width * 3);

            // loop and array indexes
            int i, j, t, k, ir, jr;
            // kernel's radius
            int radius = size >> 1;
            // color sums
            long r, g, b, div;

            byte* src = (byte*)sourceData.Scan0.ToPointer();
            byte* dst = (byte*)destData.Scan0.ToPointer();
            byte* p;

            // do the processing job
            if (sourceData.PixelFormat == PixelFormat.Format8bppIndexed)
            {
                // grayscale image

                // for each line
                for (int y = 0; y < height; y++)
                {
                    // for each pixel
                    for (int x = 0; x < width; x++, src++, dst++)
                    {
                        g = div = 0;

                        // for each kernel row
                        for (i = 0; i < size; i++)
                        {
                            ir = i - radius;
                            t = y + ir;

                            // skip row
                            if (t < 0)
                                continue;
                            // break
                            if (t >= height)
                                break;

                            // for each kernel column
                            for (j = 0; j < size; j++)
                            {
                                jr = j - radius;
                                t = x + jr;

                                // skip column
                                if (t < 0)
                                    continue;

                                if (t < width)
                                {
                                    k = kernel[i, j];

                                    div += k;
                                    g += k * src[ir * stride + jr];
                                }
                            }
                        }

                        // check divider
                        if (div != 0)
                        {
                            g /= div;
                        }
                        *dst = (g > 255) ? (byte)255 : ((g < 0) ? (byte)0 : (byte)g);
                    }
                    src += offset;
                    dst += offset;
                }
            }
            else
            {
                // RGB image

                // for each line
                for (int y = 0; y < height; y++)
                {
                    // for each pixel
                    for (int x = 0; x < width; x++, src += 3, dst += 3)
                    {
                        r = g = b = div = 0;

                        // for each kernel row
                        for (i = 0; i < size; i++)
                        {
                            ir = i - radius;
                            t = y + ir;

                            // skip row
                            if (t < 0)
                                continue;
                            // break
                            if (t >= height)
                                break;

                            // for each kernel column
                            for (j = 0; j < size; j++)
                            {
                                jr = j - radius;
                                t = x + jr;

                                // skip column
                                if (t < 0)
                                    continue;

                                if (t < width)
                                {
                                    k = kernel[i, j];
                                    p = &src[ir * stride + jr * 3];

                                    div += k;

                                    r += k * p[RGB.R];
                                    g += k * p[RGB.G];
                                    b += k * p[RGB.B];
                                }
                            }
                        }

                        // check divider
                        if (div != 0)
                        {
                            r /= div;
                            g /= div;
                            b /= div;
                        }
                        dst[RGB.R] = (r > 255) ? (byte)255 : ((r < 0) ? (byte)0 : (byte)r);
                        dst[RGB.G] = (g > 255) ? (byte)255 : ((g < 0) ? (byte)0 : (byte)g);
                        dst[RGB.B] = (b > 255) ? (byte)255 : ((b < 0) ? (byte)0 : (byte)b);
                    }
                    src += offset;
                    dst += offset;
                }
            }
        }

        private unsafe void ProcessFilter_Median(BitmapData sourceData, BitmapData destData)
        {
            size = 3;

            // get source image size
            int width = sourceData.Width;
            int height = sourceData.Height;

            int stride = sourceData.Stride;
            int offset = stride - ((sourceData.PixelFormat == PixelFormat.Format8bppIndexed) ? width : width * 3);

            // loop and array indexes
            int i, j, t;
            // processing square's radius
            int radius = size >> 1;
            // number of elements
            int c;

            // array to hold pixel values (R, G, B)
            byte[] r = new byte[size * size];
            byte[] g = new byte[size * size];
            byte[] b = new byte[size * size];

            byte* src = (byte*)sourceData.Scan0.ToPointer();
            byte* dst = (byte*)destData.Scan0.ToPointer();
            byte* p;

            // do the processing job
            if (sourceData.PixelFormat == PixelFormat.Format8bppIndexed)
            {
                // grayscale image

                // for each line
                for (int y = 0; y < height; y++)
                {
                    // for each pixel
                    for (int x = 0; x < width; x++, src++, dst++)
                    {
                        c = 0;

                        // for each kernel row
                        for (i = -radius; i <= radius; i++)
                        {
                            t = y + i;

                            // skip row
                            if (t < 0)
                                continue;
                            // break
                            if (t >= height)
                                break;

                            // for each kernel column
                            for (j = -radius; j <= radius; j++)
                            {
                                t = x + j;

                                // skip column
                                if (t < 0)
                                    continue;

                                if (t < width)
                                {
                                    g[c++] = src[i * stride + j];
                                }
                            }
                        }
                        // sort elements
                        Array.Sort(g, 0, c);
                        // get the median
                        *dst = g[c >> 1];
                    }
                    src += offset;
                    dst += offset;
                }
            }
            else
            {
                // RGB image

                // for each line
                for (int y = 0; y < height; y++)
                {
                    // for each pixel
                    for (int x = 0; x < width; x++, src += 3, dst += 3)
                    {
                        c = 0;

                        // for each kernel row
                        for (i = -radius; i <= radius; i++)
                        {
                            t = y + i;

                            // skip row
                            if (t < 0)
                                continue;
                            // break
                            if (t >= height)
                                break;

                            // for each kernel column
                            for (j = -radius; j <= radius; j++)
                            {
                                t = x + j;

                                // skip column
                                if (t < 0)
                                    continue;

                                if (t < width)
                                {
                                    p = &src[i * stride + j * 3];

                                    r[c] = p[RGB.R];
                                    g[c] = p[RGB.G];
                                    b[c] = p[RGB.B];
                                    c++;
                                }
                            }
                        }

                        // sort elements
                        Array.Sort(r, 0, c);
                        Array.Sort(g, 0, c);
                        Array.Sort(b, 0, c);
                        // get the median
                        t = c >> 1;
                        dst[RGB.R] = r[t];
                        dst[RGB.G] = g[t];
                        dst[RGB.B] = b[t];
                    }
                    src += offset;
                    dst += offset;
                }
            }
        }

    }

    public class RGB
    {
        public const short R = 2;
        public const short G = 1;
        public const short B = 0;

        public byte Red;
        public byte Green;
        public byte Blue;

        public System.Drawing.Color Color
        {
            get { return Color.FromArgb(Red, Green, Blue); }
            set
            {
                Red = value.R;
                Green = value.G;
                Blue = value.B;
            }
        }

        public RGB() { }

        public RGB(byte red, byte green, byte blue)
        {
            this.Red = red;
            this.Green = green;
            this.Blue = blue;
        }

        public RGB(System.Drawing.Color color)
        {
            this.Red = color.R;
            this.Green = color.G;
            this.Blue = color.B;
        }
    };
}